package mini.expr.lists;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import mini.Proc;
import mini.expr.Element;
import mini.expr.Expr;
import mini.memory.MemoryManager;


public class Sequence extends Expr {
	private static int counter;
    /** 
     * This field is only used when using our dynamic memory implementation. 
     * **/
    private Integer memoryAddress;
    
    /** 
     * Indicates whether elements are stored in the Heap or in the built in List
     * data structure.
     */
    private boolean usesHeap;
    
    private List<Element> elements = new ArrayList<Element>();
    private HashMap<String, Element> tmpNametable;
    
    /**
     * Stores expressions until they are evaluated.
     */
    private List<Expr> exprs = new ArrayList<Expr>();
    
    public Sequence() {
    }
    
    public Sequence(Expr e) {
        exprs.add(e);
    }
    
    public void add(Expr e) {
        if (usesHeap) {
            exprs.add(e);
        } else {
            exprs.add(0, e);
        }
    }
    
    public List<Element> getElements() {
        return elements;
    }
    
    public int numElements() {
        if (usesHeap) {
            return getElements().size();
        }
        
        return elements.size();
    }
    
    public Integer getMemoryAddress() {
        return memoryAddress;
    }

    public void setMemoryAddress(Integer memoryAddress) {
        this.memoryAddress = memoryAddress;
        
        if (this.memoryAddress != null) {
        	this.elements = MemoryManager.getInstance().loadList(this.memoryAddress);
        }
    }
    
    public boolean isUsesHeap() {
        return usesHeap;
    }

    public void setUsesHeap(boolean usesHeap) {
        this.usesHeap = usesHeap;
    }

    @Override
    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
    	
        for (Expr expr : exprs) {
            if (usesHeap) {
                Element thisElement = new Element(new ElementList());
                Sequence sequence = thisElement.getListValue().getSequence();
                
                sequence.setUsesHeap(true);
                sequence.setMemoryAddress(this.memoryAddress);
                
                HeapCons cons = new HeapCons(expr, thisElement);
                HashMap<String, Element> nt = nametable;
                

                nt = new HashMap<String, Element>();
                
                for(String key : nametable.keySet()) {
                    nt.put(key, nametable.get(key));
                }
                    
                if (tmpNametable != null) {
                    for (String key : tmpNametable.keySet()) {
                        nt.put(key, tmpNametable.get(key));
                    }
                }
                
                Element tmpElement = cons.eval(nt, functiontable, var);
                
                if (tmpNametable == null) {
                    tmpNametable = new HashMap<String, Element>();
                }
                
                tmpNametable.put(String.format("seqTmpVar%d", counter++), tmpElement);
                
                this.memoryAddress = tmpElement.getListValue().getSequence().getMemoryAddress();
                this.elements = tmpElement.getListValue().getSequence().elements;
            } else {
                //If the heap isn't being used, just store the element in the list.
                elements.add(expr.eval(nametable, functiontable, var));
            }
        }
        
        exprs.clear();
        tmpNametable = null;
        
        return null;
    }
    
    public void addToFront(Element element) {
        elements.add(0, element);
    }

    public Sequence clone() {
        Sequence newSeq = new Sequence();
        List<Element> newElements = new ArrayList<Element>();
        
        newSeq.elements = newElements;
        newSeq.memoryAddress = this.memoryAddress;
        newSeq.usesHeap = this.usesHeap;
        
        for(Element element : this.elements) {
            newElements.add(element.clone());
        }
        
        return newSeq;
    }
}
